"""
Модуль для работы с файлами и генерации объектов с координатами в 3D пространстве

Функции:
- read_config: чтение конфигурации из YAML файла
- get_objects: получение объектов из CSV файла
- save_objects: сохранение сета данных Pandas в CSV файл
- generate_points_random: генерация объектов с произвольным расположением
- generate_points_grid: генерация объектов с расположением сетью с равными сторонами
- generate_points_rows: генерация объектов с расположением ровными рядами
"""
import os as files
import pandas as pd
import numpy as np
import yaml

def read_config(file_path):
    """
    Чтение конфигурации из YAML файла 

    :param file_path: Название файла в основном каталоге

    :return: Массив параметров модуля
    """

    current_directory = files.path.dirname(files.path.abspath(__file__))

    config_file_path = files.path.join(current_directory, '..', file_path)

    with open(config_file_path, "r", encoding="utf-8") as file:
        config = yaml.safe_load(file)

    return config

def get_data(directory = 'data', filename = "data.csv"):
    """
    Получение объектов из CSV файла

    :param directory: Директория с файлами (по умолчанию 'data')
    :param filename: Имя файла (по умолчанию 'data')

    :return: Сет данных Pandas из файла
    """

    current_directory = files.path.dirname(files.path.abspath(__file__))

    # Проверяем, есть ли .csv в конце имени файла
    if not filename.endswith('.csv'):
        filename += '.csv'

    csv_file_path = files.path.join(current_directory, '..', directory, filename)

    df = pd.read_csv(csv_file_path)

    return df

def save_data(df, directory = 'data', filename = "data.csv"):
    """
    Сохранение сета данных Pandas в CSV файла

    :param df: Сет данных Pandas
    :param directory: Директория с файлами (по умолчанию 'data')
    :param filename: Имя файла (по умолчанию 'data')
    """

    current_directory = files.path.dirname(files.path.abspath(__file__))

    # Проверяем, есть ли .csv в конце имени файла
    if not filename.endswith('.csv'):
        filename += '.csv'

    csv_file_path = files.path.join(current_directory, '..', directory, filename)

    df.to_csv(csv_file_path, index=False)

    print(f"Сет данных успешно сохранён в файл {filename} в директории {directory}", end="\n\n")

def generate_points_random(num_points = 100, square_side = 5000, z = 1000, deviation_z = 50):
    """
    Генерация объектов в 3D пространстве с произвольным расположением

    :param num_points: Количество объектов (по умолчанию 100).
    :param square_side: Длина стороны пространства (по умолчанию 5000).
    :param z: Высота (по умолчанию 1000).

    :return: Сет данных с координатами X, Y, Z.
    """

    x_values = np.random.uniform(0, square_side, num_points)
    y_values = np.random.uniform(0, square_side, num_points)
    z_values = np.random.uniform(z - deviation_z, z, num_points)

    points = np.column_stack((x_values, y_values, z_values))

    points = points.round(2)

    df_coordinates = pd.DataFrame(points, columns=list('xyz'))

    df_coordinates.insert(0, 'object_id', range(1, len(df_coordinates) + 1))

    df_coordinates['name'] = 'Object #' + df_coordinates['object_id'].astype(str)

    df_coordinates = df_coordinates[['object_id', 'name']
    + df_coordinates.columns.difference(['object_id', 'name']).tolist()]

    return df_coordinates

def generate_points_grid(num_points=100, square_side=5000, z=1000,
                         deviation_x_y = 10, deviation_z = 50):
    """
    Генерация объектов в 3D пространстве с расположением сетью с равными сторонами

    :param num_points: Количество объектов (по умолчанию 100).
    :param square_side: Длина стороны пространства (по умолчанию 5000).
    :param z: Высота (по умолчанию 1000).
    :deviation_x_y: Разброс координат точек от точных координат сети (по умолчанию 10).
    :deviation_z: Разброс высоты от точных координат сети (по умолчанию 50).

    :return: Сет данных с координатами X, Y, Z.
    """

    rows = int(np.round(np.sqrt(num_points)))
    step = int(square_side // rows)

    x_values = []
    y_values = []
    z_values = []

    x = 0
    while x < square_side:
        x_values.append(x)
        x += step

    y = 0
    count = len(x_values)
    for _ in range(rows):
        y_values.extend([y] * count)
        y += step

    z_values = np.random.uniform(z - deviation_z, z, len(y_values))

    points = np.column_stack((x_values * rows, y_values, z_values))

    df = pd.DataFrame(points, columns=list('xyz'))

    random_numbers = np.random.uniform( 0 - deviation_x_y, deviation_x_y, size=len(df))
    random_numbers_zero = np.random.uniform(0, deviation_x_y, size=len(df))

    df['x'] = np.where(df['x'] > 0, df['x'] + random_numbers, df['x'] + random_numbers_zero)
    np.random.shuffle(random_numbers)
    np.random.shuffle(random_numbers_zero)
    df['y'] = np.where(df['y'] > 0, df['y'] + random_numbers, df['y'] + random_numbers_zero)

    df = df.round(2)

    df.insert(0, 'object_id', range(1, len(df) + 1))
    df['name'] = 'Object #' + df['object_id'].astype(str)
    df = df[['object_id', 'name'] + df.columns.difference(['object_id', 'name']).tolist()]

    return df

def generate_points_rows(num_points=100, rows=5, step=500, z=1000,
                         deviation_x_y = 10, deviation_z = 50):
    """
    Генерация объектов в 3D пространстве с расположением ровными рядами

    :param num_points: Количество объектов (по умолчанию 100).
    :param rows: Количество рядов (по умолчанию 5).
    :param step: Расстояние между объектами и рядами (по умолчанию 500).  
    :param z: Высота (по умолчанию 1000).
    :deviation_x_y: Разброс координат точек от точных координат сети (по умолчанию 10).
    :deviation_z: Разброс высоты от точных координат сети (по умолчанию 50).

    :return: Сет данных с координатами X, Y, Z.
    """
    count_x = int(num_points // rows)

    x_values = []
    y_values = []
    z_values = []

    x = 0
    square_side = count_x * step
    while x < square_side:
        x_values.append(x)
        x += step

    y = 0
    count = len(x_values)
    for _ in range(rows):
        y_values.extend([y] * count)
        y += step

    z_values = np.random.uniform(z - deviation_z, z, len(y_values))

    points = np.column_stack((y_values, x_values * rows, z_values))

    df = pd.DataFrame(points, columns=list('xyz'))
    random_numbers = np.random.uniform( 0 - deviation_x_y, deviation_x_y, size=len(df))
    random_numbers_zero = np.random.uniform(0, deviation_x_y, size=len(df))

    df['x'] = np.where(df['x'] > 0, df['x'] + random_numbers, df['x'] + random_numbers_zero)
    np.random.shuffle(random_numbers)
    np.random.shuffle(random_numbers_zero)
    df['y'] = np.where(df['y'] > 0, df['y'] + random_numbers, df['y'] + random_numbers_zero)

    df = df.round(2)

    df.insert(0, 'object_id', range(1, len(df) + 1))
    df['name'] = 'Object #' + df['object_id'].astype(str)
    df = df[['object_id', 'name'] + df.columns.difference(['object_id', 'name']).tolist()]

    return df
